package ddz.logic.game;

import ch.qos.logback.classic.Logger;
import com.kaka.notice.Message;
import com.kaka.notice.annotation.Handler;
import ddz.constants.Crypto;
import ddz.constants.ErrCode;
import ddz.constants.ErrLevel;
import ddz.constants.OpCode;
import ddz.core.*;
import ddz.core.ai.AI;
import ddz.db.dao.TableDao;
import ddz.db.service.TableTokenService;
import ddz.net.LogicDataHandler;
import ddz.net.ProtocolMessage;
import ddz.protos.Game;
import ddz.protos.GameChu;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

/**
 * 过牌
 *
 * @author zkpursuit
 */
@Handler(cmd = OpCode.cmd_chu_card, type = String.class)
public class ChuPaiHandler extends LogicDataHandler {
    private static final Logger logger = (Logger) LoggerFactory.getLogger(ChuPaiHandler.class);

    @Override
    public void execute(ProtocolMessage msg) throws Exception {
        GameChu.CsChu reqData = (GameChu.CsChu) msg.getBody();
        TableTokenService optTokenService = this.retrieveProxy(TableTokenService.class);
        TableToken tokenInfo = optTokenService.parseToken(reqData.getToken());
        long deskId = tokenInfo.getDeskId();
        TableDao deskdao = this.retrieveProxy(TableDao.class);
        Table table = deskdao.getDesk(deskId);
        if (table == null) {
            logger.error("玩家（" + msg.uid() + "）未加入任何房间");
            sendError(msg.ctx(), opcode(), ErrLevel.ERROR, ErrCode.no_join_room);
            return;
        }
        Player player = table.getPlayerByUid(msg.uid());
        if (player == null) {
            logger.error("玩家 [{}：{}] 在 [{}] 房间中无对应的数据", player.getSeatIndex(), msg.uid(), table.getId());
            sendError(msg.ctx(), opcode(), ErrLevel.ERROR, ErrCode.data_err);
            return;
        }
        int waitOperateSeatIndex = table.getWaitOperateSeatIndex();
        if (waitOperateSeatIndex != player.getSeatIndex()) {
            logger.error("还未轮到玩家 [{}：{}] 在 [{}] 中进行出牌操作", player.getSeatIndex(), msg.uid(), table.getId());
            sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.illegal_operation);
            return;
        }
//        if (desk.getStep() != tokenInfo.getStep()) {
//            logger.error("还未轮到玩家 [{}：{}] 在 [{}] 中进行出牌操作", player.getSeatIndex(), msg.uid(), desk.getId());
//            sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.illegal_operation);
//            return;
//        }
        Cards handCards = player.getMycards();
        Cards selfChuCards = new Cards();
        Game.Cards protoCards = reqData.getCards();
        final int count = protoCards.getCardCount();
        int[] protoCardArray = new int[count]; //保存进出牌记录的牌
        for (int i = 0; i < count; i++) {
            int card = protoCards.getCard(i);
            protoCardArray[i] = card;
            //分离出被癞子替代的牌用于常规牌型判断
            card = Cards.spliteForeCard(card);
            boolean addFlag = false;
            try {
                addFlag = selfChuCards.addCard(card);
            } catch (Exception | Error ex) {
                logger.error("用户传入的牌数据错误", ex);
            }
            if (!addFlag) {
                logger.error("玩家 [{}：{}] 在 [{}] 房间中出牌 {}", player.getSeatIndex(), msg.uid(), table.getId(), protoCards.toString());
                sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.card_type_err);
                return;
            }
            card = Cards.spliteBackCard(protoCardArray[i]);
            if (card > 0) {
                if (!handCards.hasCard(card)) {
                    logger.error("玩家 [{}：{}] 在 [{}] 房间中出牌 {}", player.getSeatIndex(), msg.uid(), table.getId(), protoCards.toString());
                    sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.card_type_err);
                    return;
                }
            }
        }

        GameMode mode = GameMode.getModeById(table.getMode());
        boolean judgeLianZha = false;
        if (GameMode.LIAN_ZHA == mode) {
            judgeLianZha = true;
        } else if (GameMode.LIAN_WIN == mode) {
            if (table.getPlace() == GameMode.LIAN_ZHA.getId()) {
                judgeLianZha = true;
            }
        }

        CardType type = AI.judgeType(selfChuCards, judgeLianZha);
        if (type == CardType.none) {
            logger.error("玩家 [{}：{}] 在房间[{}]打出的牌 {} 牌型错误 手牌：{}", player.getSeatIndex(), msg.uid(), table.getId(), Arrays.toString(selfChuCards.getCards()), Arrays.toString(player.getMycards().getCards()));
            sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.card_type_err);
            return;
        }

        boolean over = false;
        int nextRoundStartSeatIdx = table.getNextRoundStartSeatIndex(); //本轮出牌方
        if (player.getSeatIndex() == nextRoundStartSeatIdx) {
            //一轮开始时的出牌方
            over = true;
        } else {
            ChuRecord lastRecord = table.getLastChuRecord();
            if (lastRecord.getType() != CardType.lianzha && type == CardType.lianzha) {
                //上家不是连炸，但本家出的连炸
                over = true;
            } else if (lastRecord.getType() != CardType.zha && lastRecord.getType() != CardType.lianzha && type == CardType.zha) {
                //上家打出非炸弹非连炸，本家用炸弹压
                over = true;
            } else if (lastRecord.getType() == type) {
                if (type == CardType.zha && selfChuCards.getCardCount() == 2) { //都是炸弹
                    //上家出炸弹，本家出王炸
                    over = true;
                } else if (type == CardType.lianzha && selfChuCards.getCardCount() > lastRecord.getDiscardCards().length) { //都是连炸
                    //本家出的连炸数量比上家打出的多
                    over = true;
                } else {
                    //余下的炸弹或连炸或其它牌型必须保证数量一致
                    if (lastRecord.getDiscardCards().length != selfChuCards.getCardCount()) {
                        //牌张数与上家打出的牌张数不同
                        logger.error("玩家 [{}：{}] 在房间[{}]打出的牌 {} 牌型错误  手牌：{}", player.getSeatIndex(), msg.uid(), table.getId(), Arrays.toString(selfChuCards.getCards()), Arrays.toString(player.getMycards().getCards()));
                        sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.card_type_err);
                        return;
                    }
                    Cards prevChuCards = new Cards();
                    int[] _prevChuCards = lastRecord.getDiscardCards();
                    for (int card : _prevChuCards) {
                        card = Cards.spliteForeCard(card);
                        prevChuCards.addCard(card);
                    }
                    Pile prevMin = AI.getMinPile(prevChuCards, type);
                    Pile selfMin = AI.getMinPile(selfChuCards, type);
                    if (selfMin.compare(prevMin) > 0) {
                        over = true;
                    }
                }
            }
        }
        if (!over) {
            logger.error("玩家 [{}：{}] 在房间[{}]打出的牌 {} 牌不能压死上家打出的牌", player.getSeatIndex(), msg.uid(), table.getId(), Arrays.toString(selfChuCards.getCards()));
            sendError(msg.ctx(), opcode(), ErrLevel.WARN, ErrCode.not_over_pre_card);
            return;
        }

        ChuRecord record = new ChuRecord(OptType.chu, player.getSeatIndex(), protoCardArray, type);
        table.addOptRecord(record);

        for (int i = 0; i < count; i++) {
            int card = protoCards.getCard(i);
            if (card > 1000) {
                card = Cards.spliteBackCard(card);
            }
            handCards.removeCard(card);
        }
        int[] remainingCards = handCards.getCards();
        if (remainingCards == null || remainingCards.length == 0) {
            record.setFinished(true);
        }

        boolean isMingPai = table.isMingPai(waitOperateSeatIndex);

        Player[] players = table.getPlayers();
        int[] beis = table.getMultiple();
        for (Player member : players) {
            GameChu.ScChu.Builder builder = GameChu.ScChu.newBuilder()
                    .setOpt(OptType.chu.getId())
                    .setSeat(player.getSeatIndex())
                    .setType(type.getId())
                    .setCards(protoCards)
                    .setBei(beis[member.getSeatIndex()]);
            Game.Cards.Builder remaingCardsBuilder = Game.Cards.newBuilder();
            if (remainingCards != null && remainingCards.length > 0) {
                for (int i = 0; i < remainingCards.length; i++) {
                    if (member.getUid() == player.getUid() || isMingPai) {
                        remaingCardsBuilder.addCard(remainingCards[i]);
                    } else {
                        remaingCardsBuilder.addCard(0);
                    }
                }
            }
            builder.setRemains(remaingCardsBuilder.build());
            if (player.getSeatIndex() == nextRoundStartSeatIdx) {
                builder.setIsRoundStart(1);
            } else {
                builder.setIsRoundStart(0);
            }
            this.sendData(member.getUid(), Crypto.isCrypto, opcode(), builder.build().toByteArray());
        }

        //更新记牌器
        sendMessage(new Message(OpCode.cmd_counter, table));

        if (logger.isInfoEnabled()) {
            logger.info("玩家 [{}：{}] 在 [{}] 房间中出牌 {} 剩余的牌：{}  倍数：{}", player.getSeatIndex(), msg.uid(),
                    table.getId(), Arrays.toString(protoCardArray),
                    Arrays.toString(remainingCards), Arrays.toString(beis));
        }

        if (remainingCards == null || remainingCards.length == 0) {
            //全部出完
            table.setState(TableState.jiesuan);
            if (table.getMode() == GameMode.LIAN_WIN.getId()) {
                this.sendMessage(new Message(OpCode.cmd_lian_win_jiesuan, new Object[]{table, player.getSeatIndex()}));
            } else {
                this.sendMessage(new Message(OpCode.cmd_jiesuan, new Object[]{table, player.getSeatIndex()}));
            }
            table.reset(); //此处必须在结算下面，因为需要用到records数据
            table.setBankerSeatIndex(player.getSeatIndex());
        } else {
            this.sendMessage(new Message(OpCode.cmd_show_opts, table));
        }

    }
}
